今天來紀錄下遇過的問題,在元件中遇到透過 useContext 取值時可能會遇到的問題及解決方法!
接下來來介紹可能遇到的兩種報錯情形:
首先來看範例程式碼,透過在 App 層級創建 Context
並包覆子層級 TodoList
,後傳入單一值 todoData
,並於子層級將 todoData 取出來使用後渲染列表:
父層級
import React, { useState, useEffect } from "react";
import "./App.css";
import TodoList from "./Loading";
import axios from "axios";
export const TodoDataContext = React.createContext();
const App = () => {
const [todoData, setTodoData] = useState(null);
const fetchData = async () => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/todos`
);
setTodoData(response.data);
};
useEffect(() => {
fetchData();
}, []);
return (
<>
<TodoDataContext.Provider value={todoData}>
<TodoList />
</TodoDataContext.Provider>
</>
);
};
export default App;
子層級
import React, { useContext } from "react";
import { TodoDataContext } from "./App.js";
const TodoList = () => {
const todoData = useContext(TodoDataContext);
return (
<>
<ul>
{todoData &&
todoData.map((item) => <li key={item.id}>{item.title}</li>)}
</ul>
</>
);
};
export default TodoList;
在撰寫測試時會發現即使透過 waitFor
非同步等待回傳值,依然無法成功等到值:
測試
import { render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import TodoList from "./Loading";
// delectus aut autem 為 api 回傳值
test("Test get value from Context", async () => {
render(<TodoList />);
await waitFor(() => {
expect(screen.getByText("delectus aut autem")).toBeInTheDocument();
});
});
修改一下上方的範例程式碼,將傳送的從純值,改成傳送物件,物件內放入一個函式 fetchData
,再於子層級將 fetchData
拿出來使用!
父層級
import React from "react";
import "./App.css";
import TodoList from "./Loading";
import axios from "axios";
export const TodoDataContext = React.createContext();
const App = () => {
const fetchData = async () => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/todos`
);
return response.data;
};
return (
<>
<TodoDataContext.Provider value={{fetchData}}>
<TodoList />
</TodoDataContext.Provider>
</>
);
};
export default App;
子層級
import React, { useContext, useEffect, useState } from "react";
import { TodoDataContext } from "./App.js";
const TodoList = () => {
const { fetchData } = useContext(TodoDataContext);
const [todoData, setTodoData] = useState(null);
const init = async () => {
const data = await fetchData();
setTodoData(data);
};
useEffect(() => {
init();
}, []);
return (
<>
<ul>
{todoData &&
todoData.map((item) => <li key={item.id}>{item.title}</li>)}
</ul>
</>
);
};
export default TodoList;
測試的部分程式碼與問題一相同,但能觀察到報不一樣的錯誤:
import { render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import TodoList from "./Loading";
// delectus aut autem 為 api 回傳值
test("Test get value from Context", async () => {
render(<TodoList />);
await waitFor(() => {
expect(screen.getByText("delectus aut autem")).toBeInTheDocument();
});
});
判斷測試 render
時無法取得從 Context
取得的值,所以透過引入 TodoDataContext
於 render 時包覆元件,並傳入 mock 的 fetchData
解決報錯。
import { render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import { TodoDataContext } from "./App.js";
import TodoList from "./Loading";
test("Test get value from Context", async () => {
const fetchData = jest
.fn()
.mockReturnValue([{ id: "test", title: "delectus aut autem" }]);
render(
<TodoDataContext.Provider value={{ fetchData }}>
<TodoList />
</TodoDataContext.Provider>
);
await waitFor(() => {
expect(screen.getByText("delectus aut autem")).toBeInTheDocument();
});
});
可以發現測試順利運行通過:
之前常常卡在 Context
取得的資料上透過這個方法就能成功運行測試了!